﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web.Mvc;
using System.Linq.Dynamic;

namespace Softwarehouse.Repository
{
    public abstract class GenericDataGrid<TKeyType, TModel> : Controller
        where TModel : new()
    {
        public IMvcCrudRepository<TKeyType, TModel> repository;
        private readonly string gridName;
        private readonly string gridHeading;
        private readonly string jsonUrl;
        private readonly string deleteString;
        private readonly string editString;

        public string dateTimeFormat = "dd-MMM-yyyy";

        private readonly DataGridRow[] data_rows;

        protected GenericDataGrid(string gridName, string gridHeading, string jsonUrl, string editString, string deleteString, DataGridRow[] data_rows)
        {
            this.gridName = gridName;
            this.gridHeading = gridHeading;

            this.jsonUrl = jsonUrl;
            this.editString = editString;
            this.deleteString = deleteString;

            this.data_rows = data_rows;
        }

        public ActionResult GenerateGridData(string sidx, string sord, int page, int rows, bool _search, string searchField, string searchOper, string searchString)
        {
        //public JsonResult GenerateGridData(string sidx, string sord, int page, int num_rows)
        //{
            //Generate Variables
            int pageIndex = Convert.ToInt32(page) - 1;
            int pageSize = rows;
            int totalRecords = repository.All().Count();
            int totalPages = (int)Math.Ceiling((float)totalRecords / pageSize);

            //Get list
            IQueryable<TModel> items = repository.All();
            
            
            if (_search)
            {
                //check type of string or dateTime
                Type final_type = getFinalType(searchField);

                object searchObject;
                if (final_type == typeof (string))
                {
                    searchObject = searchString;
                }
                else
                {
                    MethodInfo parse = final_type.GetMethod("Parse", new [] { typeof(string) });
                    searchObject = parse.Invoke(null, new object[] { searchString });
                }

                switch (searchOper)
                {
                    case ("eq"):
                        items = items.Where(string.Format("{0} == @0", searchField), searchObject);
                        break;
                    case ("ne"):
                        items = items.Where(string.Format("{0} != @0", searchField), searchObject);
                        break;
                    case ("lt"):
                        items = items.Where(string.Format("{0} < @0", searchField), searchObject);
                        break;
                    case ("le"):
                        items = items.Where(string.Format("{0} <= @0", searchField), searchObject);
                        break;
                    case ("gt"):
                        items = items.Where(string.Format("{0} > @0", searchField), searchObject);
                        break;
                    case ("ge"):
                        items = items.Where(string.Format("{0} >= @0", searchField), searchObject);
                        break;
                }
            }

            items = items
                .OrderBy(sidx + " " + sord)
                .Skip(pageIndex * pageSize)
                .Take(pageSize);
            //Generate JSON
            var jsonData = new
            {
                total = totalPages,
                page,
                records = totalRecords,
                rows = (items.Select(p => new
                {
                    //id column from repository
                    i = typeof(TModel).GetProperty(repository.primaryKey).GetValue(p, null),
                    cell = getCells(p)
                })).ToArray()
            };
            return Json(jsonData);
        }

        private string[] getCells(TModel p)
        {
            List<string> result = new List<string>();

            if(!string.IsNullOrEmpty(editString))
            {
                PropertyInfo c = typeof(TModel).GetProperty("id");
                c.GetValue(p, null);
                result.Add(string.Format("<a href='{0}' >Edit</a>", editString.Replace("?", c.GetValue(p, null).ToString())));
            }

            if (!string.IsNullOrEmpty(deleteString))
            {
                PropertyInfo c = typeof(TModel).GetProperty("id");
                c.GetValue(p, null);
                result.Add(string.Format("<a href='{0}' >Delete</a>", deleteString.Replace("?", c.GetValue(p, null).ToString())));
            }

            foreach(string column in data_rows.Select(r => r.value))
            {
                try
                {
                    //hack for tblcategory.name
                    string[] parts = column.Split('.');

                    //Set first part
                    PropertyInfo c = typeof(TModel).GetProperty(parts[0]);
                    object tmp = c.GetValue(p, null);
                    
                    //loop through if there is more than one depth to the . eg tblCategory.name
                    for (int j = 1; j < parts.Length; j++)
                    {
                        c = tmp.GetType().GetProperty(parts[j]);
                        tmp = c.GetValue(tmp, null);
                    }

                    if (tmp.GetType() == typeof(DateTime))
                    {
                        result.Add(((DateTime)tmp).ToString(dateTimeFormat));
                    }
                    else
                    {
                        result.Add(tmp.ToString());
                    }
                }
                catch (Exception)
                {
                    result.Add("");
                }
            }
            return result.ToArray();
        }

        public string renderIncludes
        {
            get
            {
                return
                    @"<link href=""/Scripts/css/ui.jqgrid.css"" rel=""stylesheet"" type=""text/css"" />
                    <link href=""/Scripts/css/redmond/jquery-ui-1.7.1.custom.css"" rel=""stylesheet"" type=""text/css"" />
                    <script src=""/Scripts/jquery-1.3.2.min.js"" type=""text/javascript""></script>
                    <script src=""/Scripts/i18n/grid.locale-en.js"" type=""text/javascript""></script>
                    <script src=""/Scripts/jquery.jqGrid.min.js"" type=""text/javascript""></script>";
            }
        }

        public string renderGrid
        {
            get
            {
                return
                    string.Format(@"<script type=""text/javascript"">
                    jQuery(document).ready(function() {{
                        jQuery(""#{0}"").jqGrid({{
                            url: '{1}',
                            datatype: ""json"",
                            colNames: [{2}],
                            colModel: [{3}],
                            rowNum: 10,
                            rowList: [10, 20, 30],
                            imgpath: ""/Scripts/css/redmond/images"",
                            pager: jQuery('#{0}_pager'),
                            sortname: 'id',
                            viewrecords: true,
                            sortorder: ""desc"",
                            caption: ""{4}""
                        }}).navGrid('#{0}_pager', {{ edit: false, add: false, del: false, search: true, refresh: true }});
                    }}); 
                </script>
                <table id=""{0}"" width=""100%"" class=""scroll"" cellpadding=""0"" cellspacing=""0""></table>
                <div id=""{0}_pager"" class=""scroll"" style="" text-align:center;""></div>", gridName, jsonUrl, headingsString, columnsString, gridHeading);
            }
        }

        public string headingsString
        {
            get
            {
                string result = "";
                string delimiter = "";

                if (!string.IsNullOrEmpty(editString))
                {
                    result += string.Format("{0}'Edit'", delimiter);
                    delimiter = ", ";
                }

                if (!string.IsNullOrEmpty(deleteString))
                {
                    result += string.Format("{0}'Delete'", delimiter);
                    delimiter = ", ";
                }

                foreach(string s in data_rows.Select(r => r.heading))
                {
                    result += string.Format("{0}'{1}'", delimiter, s);
                    delimiter = ", ";
                }
                return result;
            }
        }

        public string columnsString
        {
            get
            {
                string result = "";
                string delimiter = "";

                if (!string.IsNullOrEmpty(editString))
                {
                    result += string.Format("{0}{{ name: 'edit', index: 'edit', width:40, sortable:false, search:false }}", delimiter);
                    delimiter = ", ";
                }

                if (!string.IsNullOrEmpty(editString))
                {
                    result += string.Format("{0}{{ name: 'delete', index: 'delete', width:60, sortable:false, search:false }}", delimiter);
                    delimiter = ", ";
                }
                foreach (DataGridRow row in data_rows)
                {
                    result += string.Format("{0}{{ name: '{1}', index: '{1}', width:{2} {3} }}", delimiter, row.id, row.width, string.IsNullOrEmpty(row.extraArguments) ? "" : (string.Format(", {0}", row.extraArguments)));
                    delimiter = ", ";
                }
                return result;
            }
        }

        private static Type getFinalType(string input)
        {
            //hack for tblcategory.name
            string[] parts = input.Split('.');

            //Set first part
            PropertyInfo c = typeof(TModel).GetProperty(parts[0]);

            //loop through if there is more than one depth to the . eg tblCategory.name
            for (int j = 1; j < parts.Length; j++)
            {
                c = c.PropertyType.GetProperty(parts[j]);
            }
            return c.PropertyType;

        }
    }

    public class DataGridRow
    {
        public string value;
        public string id;
        public string heading;
        public int width;
        public string extraArguments;

        public DataGridRow(string value, string id, string heading, int width, string extraArguments )
        {
            this.value = value;
            this.id = id;
            this.heading = heading;
            this.width = width;
            this.extraArguments = extraArguments;
        }

        public DataGridRow(string value, string id, string heading) : this(value, id, heading, 55, "") { }
        public DataGridRow(string value, string id, string heading, int width) : this(value, id, heading, width, "") { }

        public DataGridRow(string value, string heading) : this(value, value, heading, 55, "") { }
        public DataGridRow(string value, string heading, int width) : this(value, value, heading, width, "") { }
        public DataGridRow(string value, string heading, int width, string extraArguments) : this(value, value, heading, width, extraArguments) { }
    }
}
